import crypto from 'crypto'

const ALGORITHM = 'aes-256-cbc' // 加密算法
const RANDOM_BYTES_SIZE = 16 // 16字节的随机数据
const MSG_LENGTH_SIZE = 4 // 4字节的消息体大小
const BLOCK_SIZE = 32 // 数据块大小

// 微信加密解密类
class WxCrypto {
  // 构造函数
  constructor (data = {}) {
    // 初始化数据
    const {
      appId,
      encodingAESKey,
      token
    } = data
    // 将encodingAESKey转成buffer
    const key = Buffer.from(encodingAESKey + '=', 'base64')
    // 截取key的前16字节作为iv
    const iv = key.slice(0, 16)
    this.data = {
      appId,
      token,
      key,
      iv
    }
  }

  // 加密
  encrypt (msg) {
    // 如果是json，则转成字符串
    if (typeof msg !== 'string') {
      msg = JSON.stringify(msg)
    }
    const {
      appId,
      key,
      iv
    } = this.data
    const randomBytes = crypto.randomBytes(RANDOM_BYTES_SIZE) // 生成指定大小的随机数据

    const msgLenBuf = Buffer.alloc(MSG_LENGTH_SIZE) // 申请指定大小的空间，存放消息体的大小
    const offset = 0 // 写入的偏移值
    msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset) // 按大端序（网络字节序）写入消息体的大小

    const msgBuf = Buffer.from(msg) // 将消息体转成 buffer
    const appIdBuf = Buffer.from(appId) // 将 APPID 转成 buffer

    let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf]) // 将16字节的随机数据、4字节的消息体大小、若干字节的消息体、若干字节的APPID拼接起来

    const cipher = crypto.createCipheriv(ALGORITHM, key, iv) // 创建加密器实例
    cipher.setAutoPadding(false) // 禁用默认的数据填充方式
    totalBuf = this.PKCS7Encode(totalBuf) // 使用自定义的数据填充方式
    const encryptdBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()]) // 加密后的数据

    return encryptdBuf.toString('base64') // 返回加密数据的 base64 编码结果
  }

  // 解密
  decrypt (encryptedMsg) {
    const {
      key,
      iv
    } = this.data
    const encryptedMsgBuf = Buffer.from(encryptedMsg, 'base64')
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
    decipher.setAutoPadding(false)
    let decryptedBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()])
    // 解密后的数据去掉填充
    decryptedBuf = this.PKCS7Decode(decryptedBuf)
    const msgSize = decryptedBuf.readUInt32BE(RANDOM_BYTES_SIZE)
    const msgBufStartPos = RANDOM_BYTES_SIZE + MSG_LENGTH_SIZE
    const msgBufEndPos = msgBufStartPos + msgSize
    const msgBuf = decryptedBuf.slice(msgBufStartPos, msgBufEndPos)
    const appIdBuf = decryptedBuf.slice(msgBufEndPos)
    const text = msgBuf.toString()
    const appId = appIdBuf.toString()
    let value
    try {
      value = JSON.parse(text)
    } catch (err) {
      value = text
    }
    return {
      value,
      text,
      appId
    }
  }

  // 获取消息签名
  getMsgSign (params) {
    const { token } = this.data
    const {
      timestamp, // 时间戳
      nonce, // 随机数
      encrypt // 加密数据
    } = params
    const rawStr = [token, timestamp, nonce, encrypt].sort().join('')
    const signature = crypto.createHash('sha1').update(rawStr).digest('hex')
    return signature
  }

  // 验证消息签名
  verifyMsgSign (params) {
    return this.getMsgSign(params) === params.msg_signature
  }

  // 获取响应签名
  getVerifyResponseSign (params) {
    const { token } = this.data

    const {
      timestamp, // 时间戳
      nonce // 随机数
    } = params

    const tmpArr = [token, timestamp, nonce]
    tmpArr.sort((a, b) => a.localeCompare(b))
    const tmpStr = tmpArr.join('')
    const signature = crypto.createHash('sha1').update(tmpStr).digest('hex')
    return signature
  }

  // 验证响应签名
  verifyResponseSign (params) {
    return this.getVerifyResponseSign(params) === params.signature
  }

  // PKCS7填充
  PKCS7Decode (buf) {
    const padSize = buf[buf.length - 1]
    return buf.slice(0, buf.length - padSize)
  }

  // PKCS7解填充
  PKCS7Encode (buf) {
    const padSize = BLOCK_SIZE - (buf.length % BLOCK_SIZE)
    const fillByte = padSize
    const padBuf = Buffer.alloc(padSize, fillByte)
    return Buffer.concat([buf, padBuf])
  }
}

export default WxCrypto
